
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using Swaf;
using Swaf.Container;

namespace Swaf.Crypto
{
    /// <summary>
    /// Summary description for SimpleFileKeyMaster.
    /// </summary>
    public class SimpleFileKeyMaster : BaseKeyMaster
    {
		protected static string s_defKeyFileName = "SK.dat";
        protected IDictionary m_keys = new Hashtable();
        protected string m_persistFileName = s_defKeyFileName;
        protected string m_encryptedPersistFileName = String.Empty;
        protected bool m_useRandomKeys = false;
        private string m_containerName = "SwafKeyContainer";
        private Random m_rand = new Random();
        private Object[] m_randomKeys;
        private const string KEY_FILE_MACRO = "$KeyFileName$";
        private const string KEY_FILE_LOCATION_MACRO = "$KeyFileLocation$";
        private const string KEY_CONTAINER_NAME = "$KeyContainerName$";
        private const string USE_RANDOM_KEYS = "$UseRandomKeys$";
        private string m_strUseRandomKeys = USE_RANDOM_KEYS;

        public bool UseRandomKeys
        {
            get
            {
                if (m_strUseRandomKeys.Equals(USE_RANDOM_KEYS))
                {
                    // Recheck to try and resolve the macro
                    m_strUseRandomKeys = Application.currentApp.resMgr.resolve(USE_RANDOM_KEYS);
                }

                if (!m_strUseRandomKeys.Equals(USE_RANDOM_KEYS))
                {
                    m_useRandomKeys = Convert.ToBoolean(m_strUseRandomKeys);
                }
                else
                {
                    m_useRandomKeys = false;
                }

                return m_useRandomKeys;
            }
            set
            {
                m_useRandomKeys = value;
            }
        }
        public SimpleFileKeyMaster()
        {
            IApplication app = Application.currentApp;
            string keyFile = app.resMgr.resolve(KEY_FILE_MACRO);
            string keyFileLoc = app.resMgr.resolve(KEY_FILE_LOCATION_MACRO);
            string keyContainerName = app.resMgr.resolve(KEY_CONTAINER_NAME);
            m_strUseRandomKeys = app.resMgr.resolve(USE_RANDOM_KEYS);

            if (keyFile.Equals(KEY_FILE_MACRO))
                keyFile = m_persistFileName;
            if (keyFileLoc.Equals(KEY_FILE_LOCATION_MACRO))
				keyFileLoc = Directory.GetCurrentDirectory();
            if (!keyFileLoc.EndsWith(@"\"))
                keyFileLoc += @"\";
			if (keyFileLoc.StartsWith(@"file://", StringComparison.OrdinalIgnoreCase))
				keyFileLoc = keyFileLoc.Substring(@"file://".Length);
            if (keyContainerName != KEY_CONTAINER_NAME)
                m_containerName = keyContainerName;

            m_persistFileName = keyFileLoc.ToLower() + keyFile.ToLower();

            if (!m_persistFileName.EndsWith(".enc"))
            {
                if (Path.HasExtension(m_persistFileName))
                    m_encryptedPersistFileName = Path.ChangeExtension(m_persistFileName, "enc");
                else
                    m_encryptedPersistFileName = m_persistFileName + ".enc";
            }
            else
                m_encryptedPersistFileName = m_persistFileName;

            readKeys();
        }
        #region IKeyMaster Members

        public override IDictionary Keys { get { return new ReadOnlyDictionary(m_keys); } }

        public override void removeKeyInfo(string name, bool persist)
        {
            if (m_keys.Contains(name))
                m_keys.Remove(name);

            if (UseRandomKeys)
            {
                // Copy the contents into the array so it can be indexed
                m_randomKeys = new Object[m_keys.Count];
                m_keys.CopyTo(m_randomKeys, 0);
            }

            if (persist)
                writeKeys();
        }

        public override IKeyInfo getKeyInfo(string name)
        {
            // TODO: We should be able to do random keys for any particular key name
            // this only supports the default key
            if (UseRandomKeys && name.Length <= 0)
            {
                // Get a random index value
                Int32 randIndex = m_rand.Next(m_keys.Count);

                // Return the key baed on the random index
                DictionaryEntry entry = (DictionaryEntry)m_randomKeys[randIndex];
                return (IKeyInfo)entry.Value;
            }
            else
            {
                return (IKeyInfo)m_keys[name];
            }
        }

        public override void generateKeyInfo(string name, string type, bool persist)
        {
            ICryptoHandler h = this.getHandler(type);
            if (h != null)
                m_keys[name] = new KeyInfo(name, type, h.generateKey(), persist);

            if (UseRandomKeys)
            {
                // Copy the contents into the array so it can be indexed
                m_randomKeys = new Object[m_keys.Count];
                m_keys.CopyTo(m_randomKeys, 0);
            }

            if (persist)
                writeKeys();
        }

        /// <summary>
        /// Generate a set of random keys based on a combination of name, type, and number of keys to generate
        /// </summary>
        public override void generateKeyInfo(string name, string type, int numRandomKeys, bool persist)
        {
            if (UseRandomKeys)
            {
                ICryptoHandler h = this.getHandler(type);
                if (h != null)
                {
                    // Generate the random keys 
                    for (Int32 i = 0; i <= numRandomKeys; i++)
                    {
                        String nameHash = Convert.ToBase64String(Encoding.UTF8.GetBytes(name + "_" + i));
                        m_keys[nameHash] = new KeyInfo(nameHash, type, h.generateKey(), persist);
                    }

                    // Copy the contents into the array so it can be indexed
                    m_randomKeys = new Object[m_keys.Count];
                    m_keys.CopyTo(m_randomKeys, 0);

                    // Persist the keys to file
                    if (persist)
                        writeKeys();
                }
            }
            else
                generateKeyInfo(name, type, persist);
        }

        #endregion

        protected void writeKeys()
        {
            lock (this)
            {
                // Increase the default buffer of 1024 to facilitate a larger key file
                StringStream str = new StringStream(m_keys.Count * 1000);

                foreach (DictionaryEntry e in m_keys)
                {
                    KeyInfo ki = (KeyInfo)e.Value;
                    if (ki.Persist)
                    {
                        str.writeString(ki.Name);
                        str.writeString(ki.Type);
                        str.writeInt(ki.Key.Length);
                        foreach (byte[] b in ki.Key)
                            str.writeString(Convert.ToBase64String(b));
                    }
                }

                // Check for the existance of an encrypted file
                if (File.Exists(m_encryptedPersistFileName))
                {
                    // Encrypt the data
                    CryptoManager cm = new CryptoManager(Application.currentApp);
                    cm.encryptFileEx(str.ToString(), m_encryptedPersistFileName);
                }
                else
                {
                    // Make sure the file is not read-only before attempting to update
                    File.SetAttributes(m_persistFileName, FileAttributes.Normal);

                    // Create the file and write the contents
                    StreamWriter fs = File.CreateText(m_persistFileName);
                    fs.Write(str.ToString());

                    // Close the stream
                    fs.Close();
                }
            }
        }
        protected void readKeys()
        {
            try
            {
                lock (this)
                {
					IApplication app = Application.currentApp;
                    StringStream str = null;

					if (!Cmn.IsEmpty(app.resMgr.getMacro("RootRezNamespace")))
					{
						string rez = app.resMgr.getMacro("RootRezNamespace") + s_defKeyFileName;

						if(AssRez.Instance.rezExist(rez))
							using (TextReader r = new StreamReader(AssRez.Instance.getResource(rez)))
							{
								string rezTxt = r.ReadToEnd();
								str = new StringStream(rezTxt);
							}
					}
					if (str == null)
					{
						// Check for the existance of an encrypted file
						if (File.Exists(m_encryptedPersistFileName))
						{
							// Read the decrypted contents into the stream
							//CryptoManager cm = new CryptoManager(Application.currentApp);
							ICryptoManagerEx cm = Application.currentApp.appData["sysObjs-Crypto"] as ICryptoManagerEx;
							if (cm == null)
							{
								//CryptoManager cm = new CryptoManager(Application.currentApp);
								cm = new CryptoManager(Application.currentApp);
							}

							str = new StringStream(cm.decryptFileEx(m_encryptedPersistFileName));
						}
						else
						{
							FileInfo f = new FileInfo(m_persistFileName);
							Application.currentApp.Log.println(Application.VERBOSE_STARTUP_LOG_LEVEL, "--> SimpleFileKeyMaster.readKeys -->Opening file {0} (Exists = {1}, Path={2})...", m_persistFileName, f.Exists, f.FullName);

							StreamReader r = f.OpenText();
							Application.currentApp.Log.println(Application.VERBOSE_STARTUP_LOG_LEVEL, "--> SimpleFileKeyMaster.readKeys -->Opened file {0} ...", m_persistFileName);
							str = new StringStream(r.ReadToEnd());
							r.Close();
						}
					}

                    while (!str.eof)
                    {
                        string name = str.readString();
                        string type = str.readString();
                        object[] key = new object[str.readInt()];
                        for (int p = 0; p < key.Length; ++p)
                            key[p] = Convert.FromBase64String(str.readString());

                        KeyInfo ki = new KeyInfo(name, type, key, true);
                        m_keys[ki.Name] = ki;
                    }

                    //if(UseRandomKeys)
                    //{
                    // Copy the contents into the array so it can be indexed
                    m_randomKeys = new Object[m_keys.Count];
                    m_keys.CopyTo(m_randomKeys, 0);
                    //}
                }
                Application.currentApp.Log.println(Application.VERBOSE_STARTUP_LOG_LEVEL, "Successfully read contents of SimpleKeys.dat.  Read in {0} keys.", m_keys.Count);
            }
            catch (Exception e)
            {
                Application.currentApp.Log.println("Exception reading SimpleFileKeyMaster.dat. {0}", e.ToString());
                throw new BaseException(string.Format("Exception reading SimpleFileKeyMaster.dat: {0}",m_persistFileName), e);
            }
        }
    }
}
